package com.sloan.community.service;

import com.sloan.community.common.Constant;
import com.sloan.community.common.dto.CommentCountDTO;
import com.sloan.community.common.query.SearchQuery;
import com.sloan.community.common.vo.IndexArticleVO;
import com.sloan.community.common.vo.PageVo;
import com.sloan.community.common.vo.TagArticleVO;
import com.sloan.community.common.vo.TagVO;
import com.sloan.community.po.*;
import com.sloan.community.repository.*;
import com.sloan.community.util.TextUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.AbstractMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

import static com.sloan.community.util.TextUtils.TEXT_NUM;

@Slf4j
@Service
@RequiredArgsConstructor(onConstructor_ = {@Lazy})
public class TagService {

    private final TagRepository tagRepository;
    private final ArticleTagRepository articleTagRepository;
    private final ArticleRepository articleRepository;
    private final ArticleContentRepository articleContentRepository;
    private final UserRepository userRepository;
    private final CommentRepository commentRepository;


    public Mono<TagArticleVO> list(Long tagId, SearchQuery query) {
        Mono<Tag> tagMono = tagRepository.findById(tagId)
                .switchIfEmpty(Constant.tagNotFoundException(tagId));

        Flux<Long> articleIdFlux = articleTagRepository.findByTagId(tagId)
                .map(ArticleTag::getArticleId)
                .distinct()
                .cache();

        Flux<Article> articleFlux = articleIdFlux.collectList()
                .flatMapMany(articleIds -> {
                    PageRequest pageRequest = PageRequest.of((query.getCurrent() - 1), query.getSize(),
                            Sort.by(Sort.Order.desc("created_time")));
                    return articleRepository.findByIdIsIn(articleIds, pageRequest);
                })
                .cache();

        Mono<Long> countMono = articleIdFlux.collectList().flatMap(articleRepository::countByIdIsIn);

        Mono<Map<Long, String>> contentMapMono = articleIdFlux.collectList()
                .flatMapMany(articleContentRepository::findByArticleIdIsIn)
                .collectMap(ArticleContent::getArticleId, ArticleContent::getExtractText);

        Flux<Long> userIdFlux = articleFlux.map(Article::getUserId).distinct();
        Mono<Map<Long, User>> userMapMono = userIdFlux.collectList()
                .flatMapMany(userIds -> userRepository.findByIdIsIn(new HashSet<>(userIds)))
                .collectMap(User::getId, Function.identity());

        Mono<Map<Long, Long>> commentMapMono = articleIdFlux.collectList()
                .flatMapMany(commentRepository::countByArticleIds)
                .collectMap(CommentCountDTO::getArticleId, CommentCountDTO::getCommentCount);

        Flux<ArticleTag> articleTagFlux = articleIdFlux.collectList()
                .flatMapMany(articleTagRepository::findByArticleIdIsIn)
                .cache();

        Mono<Map<Long, HashSet<Long>>> articleTagMapMono = articleTagFlux
                .groupBy(ArticleTag::getArticleId, ArticleTag::getTagId)
                .flatMap(groups -> groups.collectList()
                        .map(item -> new AbstractMap.SimpleEntry<>(groups.key(), new HashSet<>(item))))
                .collectMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue);

        Flux<Long> allTagIdFlux = articleTagFlux.map(ArticleTag::getTagId).distinct();

        Mono<Map<Long, Tag>> tagMapMono = tagRepository.findAllById(allTagIdFlux)
                .collectMap(Tag::getId, Function.identity());

        return Mono.zip(tagMono, articleFlux.collectList(), countMono, contentMapMono,
                        userMapMono, tagMapMono, commentMapMono, articleTagMapMono)
                .map(tuple -> {
                    Tag curTag = tuple.getT1();
                    Map<Long, String> contentMap = tuple.getT4();
                    Map<Long, Long> commentMap = tuple.getT7();
                    Map<Long, User> userMap = tuple.getT5();
                    Map<Long, Tag> tagMap = tuple.getT6();
                    Map<Long, HashSet<Long>> articleTagMap = tuple.getT8();
                    List<Article> articleList = tuple.getT2();

                    List<IndexArticleVO> resList =
                            getIndexArticleVOS(articleList, userMap, contentMap, commentMap, articleTagMap, tagMap);

                    PageVo<IndexArticleVO> pageVo =
                            new PageVo<>(tuple.getT3(), resList, query.getCurrent(), query.getSize(), 5, false);
                    TagArticleVO tagArticleVO = new TagArticleVO();
                    tagArticleVO.setTagId(curTag.getId());
                    tagArticleVO.setTagName(curTag.getName());
                    tagArticleVO.setPageArticleList(pageVo);
                    return tagArticleVO;
                });
    }

    private List<IndexArticleVO> getIndexArticleVOS(List<Article> articleList, Map<Long, User> userMap, Map<Long, String> contentMap,
                                                    Map<Long, Long> commentMap, Map<Long, HashSet<Long>> articleTagMap,
                                                    Map<Long, Tag> tagMap) {
        return articleList
                .stream()
                .map(article -> {
                    User user = userMap.get(article.getUserId());
                    IndexArticleVO indexArticleVO = new IndexArticleVO();
                    indexArticleVO.setArticleId(article.getId());
                    indexArticleVO.setTitle(article.getTitle());
                    indexArticleVO.setContent(TextUtils.highlightText(contentMap.get(article.getId()), "", TEXT_NUM));
                    indexArticleVO.setFirstPicture(article.getFirstPicture());
                    indexArticleVO.setView(article.getView());
                    indexArticleVO.setLikeCount(article.getLikeCount());
                    indexArticleVO.setCommentCount(commentMap.get(article.getId()));
                    indexArticleVO.setCreatedBy(article.getCreatedBy());
                    indexArticleVO.setUpdatedBy(article.getUpdatedBy());
                    indexArticleVO.setCreatedBy(article.getCreatedBy());
                    indexArticleVO.setUpdatedBy(article.getUpdatedBy());
                    indexArticleVO.setUserId(article.getUserId());
                    indexArticleVO.setUsername(user.getUsername());
                    indexArticleVO.setNickname(user.getNickname());
                    indexArticleVO.setAvatarUrl(user.getAvatarUrl());
                    indexArticleVO.setAuthFrom(user.getAuthFrom());
                    indexArticleVO.setFromT(Constant.Oauth2.getByCode(user.getAuthFrom()).getMsg());

                    List<TagVO> list = articleTagMap.get(article.getId())
                            .stream()
                            .map(aTagId -> {
                                Tag tag = tagMap.get(aTagId);
                                TagVO tagVO = new TagVO();
                                BeanUtils.copyProperties(tag, tagVO);
                                tagVO.setSelected(aTagId.equals(tag.getId()));
                                return tagVO;
                            }).toList();
                    indexArticleVO.setTags(list);

                    return indexArticleVO;
                }).toList();
    }
}
